﻿using System;
using System.Threading;
using System.ComponentModel;
using System.Data.SqlClient;
using System.Collections.Generic;

using Builderator.Core.Plugin;
using Builderator.Core.Interfaces;

namespace Builderator.Core
{
    public enum JobStatus { Idle, Running, Completed, Cancelled, Error }

    public delegate void JobStatusChangedDelegate(object sender, JobStatusEventArgs e);
    
    public class Job : Document, IDatabase, INotifyPropertyChanged
    {
        public event JobStatusChangedDelegate JobStatusChanged;
        public event PropertyChangedEventHandler PropertyChanged;
        public event DebugMessageDelegate DebugMessageEvent;

        [DocumentProperty("Identifier", typeof(Guid), false)]
        public Guid Identifier
        {
            get;
            set;
        }

        [DocumentProperty("Associated", typeof(Guid), false)]
        public Guid Associated
        {
            get;
            set;
        }

        [DocumentProperty("Name", typeof(String), false)]
        public string Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;

                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));
            }
        }

        [DocumentProperty("Status", typeof(JobStatus), false)]
        public JobStatus Status
        {
            get
            {
                return this.status;
            }
            set
            {
                this.status = value;

                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Status"));
            }
        }

        [DocumentProperty("Executed", typeof(DateTime), false)]
        public DateTime Executed
        {
            get;
            set;
        }

        private string name;
        private IPlugin plugin;
        private JobStatus status;
        private List<Job> jobList;

        public Job()
        {
            this.plugin = null;

            this.jobList = null;

            this.Identifier = Guid.NewGuid();

            this.Name = string.Empty;

            this.Status = JobStatus.Idle;
        }

        public Job(IPlugin plugin)
            : this()
        {
            this.plugin = plugin;

            this.Name = plugin.Name;
        }

        public Job(List<Job> jobList)
            : this()
        {
            this.jobList = jobList;
        }

        public void ExecuteThread()
        {
            this.Executed = DateTime.Now;

            if (this.plugin != null)
                this.ExecuteJob();
            else
                this.ExecuteJobList();
        }

        public ExecutionResult Execute()
        {
            this.Executed = DateTime.Now;

            if (this.plugin != null)
                return this.ExecuteJob();
            else
                this.ExecuteJobList();

            return ExecutionResult.Success;
        }

        private ExecutionResult ExecuteJob()
        {
            this.JobStatusChanged(this, new JobStatusEventArgs() { Status = JobStatus.Running });
            
            if (this.plugin.Execute() == ExecutionResult.Fail)
            {
                this.JobStatusChanged(this, new JobStatusEventArgs() { Status = JobStatus.Error });

                return ExecutionResult.Fail;
            }

            this.JobStatusChanged(this, new JobStatusEventArgs() { Status = JobStatus.Completed });

            return ExecutionResult.Success;
        }

        private void ExecuteJobList()
        {
            bool failed = false;

            this.DebugMessageEvent("Executing job list");

            this.JobStatusChanged(this, new JobStatusEventArgs() { Status = JobStatus.Running });

            foreach (Job job in this.jobList)
            {
                if (failed)
                {
                    job.JobStatusChanged(job, new JobStatusEventArgs() { Status = JobStatus.Error });

                    continue;
                }

                this.DebugMessageEvent(string.Format("Executing job {0}", job.Name));

                if (job.Execute() == ExecutionResult.Fail)
                {
                    failed = true;

                    this.JobStatusChanged(this, new JobStatusEventArgs() { Status = JobStatus.Error });
                }
            }

            if (!failed)
                this.JobStatusChanged(this, new JobStatusEventArgs() { Status = JobStatus.Completed });
        }

        public SqlCommand Select(DatabaseTable database)
        {
            SqlCommand command = new SqlCommand();

            command.CommandText = string.Format("SELECT * FROM jobs WHERE identifier='{0}'", this.Identifier.ToString());

            return command;
        }

        public SqlCommand Insert(DatabaseTable database)
        {
            SqlCommand command = new SqlCommand();

            command.CommandText = string.Format("INSERT INTO jobs VALUES (@ID, @NAME, @Status, @Associated, @Settings)");

            command.Parameters.AddWithValue("@ID", this.Identifier.ToString());

            command.Parameters.AddWithValue("@Name", this.Name);

            command.Parameters.AddWithValue("@Status", Enum.GetName(typeof(JobStatus), this.Status));

            command.Parameters.AddWithValue("@Associated", this.Associated.ToString());

            command.Parameters.AddWithValue("@Settings", this.Serialize());

            return command;
        }

        public SqlCommand Update(DatabaseTable database)
        {
            SqlCommand command = new SqlCommand();

            command.CommandText = string.Format("UPDATE jobs SET status='{0}', settings='{1}' WHERE identifier='{2}'", Enum.GetName(typeof(JobStatus), this.Status), this.Serialize(), this.Identifier.ToString());

            return command;
        }

        public SqlCommand Delete(DatabaseTable database)
        {
            SqlCommand command = new SqlCommand();

            command.CommandText = string.Format("DELETE FROM jobs WHERE identifier='{0}'", this.Identifier.ToString());

            return command;
        }
    }

    public class JobStatusEventArgs : EventArgs
    {
        public JobStatus Status
        {
            get;
            set;
        }

        public JobStatusEventArgs()
            : base()
        {
            this.Status = JobStatus.Idle;
        }
    }
}